#region File Description
//-----------------------------------------------------------------------------
// GameplayScreen.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion

#region Using Statements
using System;
using System.Globalization;
using System.Threading;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
#endregion

namespace Alex_s_Dream_The_Toy_World
{
    /// <summary>
    /// This screen implements the actual game logic. It is just a
    /// placeholder to get the idea across: you'll probably want to
    /// put some more interesting gameplay in here!
    /// </summary>
    public class GameplayScreen : GameScreen, IRenderChunkProvider
    {
        #region Fields

        /// <summary>
        /// Exposes <see cref="QSGame"/> to the <see cref="SceneManager"/>
        /// </summary>
        private FrameworkCore game;

        /// <summary>
        /// Exposes the <see cref="ContentManager"/>.
        /// </summary>
        private ContentManager content;

        /// <summary>
        /// All interfaces controlled by the <see cref="SceneManager"/> are run through this list
        /// </summary>
        private Dictionary<InterfaceType, Interface> interfaces;

        private List<Scene> scenes;

        private Scene activeScene;

        private readonly Dictionary<Int64, BaseEntity> entities;

        /// <summary>
        /// <see cref="SceneManager"/>'s <see cref="CameraInterface"/>
        /// </summary>
        private CameraInterface cameraInterface;
        private PhysicsInterface physicsInterface;

        private BaseEntity sceneMgrRootEntity;


        private const string name = "SceneManager";

        BaseInputComponent currentCameraInputType;

        private Int64 controlledEntity = -1;

        private GameTime gameTimeData;

        #endregion

        #region Properties

        /// <summary>
        /// Holds all of the scenes in memory. Scenes may be used to stream data in and out of the
        /// scene manager as scenes load and unload.
        /// </summary>
        public List<Scene> Scenes
        {
            get { return scenes; }
        }

        public Scene ActiveScene
        {
            get { return activeScene; }
            set
            {
                if (value != this.activeScene)
                {
                    if (this.activeScene != null)
                    {
                        // Set the current active scene to inactive
                        this.activeScene.Deactiviate();
                    }

                    this.activeScene = value;
                    this.activeScene.Activate();
                }
            }
        }


        /// <summary>
        /// A list of all the entities in this scene.
        /// </summary>
        public Dictionary<Int64, BaseEntity> Entities
        {
            get { return entities; }
        }

        /// <summary>
        /// A root entity for the scene manager to contain things like cameras.
        /// </summary>
        /// <remarks>@todo: This may be TEMPORARY. Still testing out the idea of a root entity for the camera system.</remarks>
        public BaseEntity SceneMgrRootEntity
        {
            get { return sceneMgrRootEntity; }
        }

        public bool NeedBackgroundAsTexture
        {
            get { return false; }
        }


        /// <summary>
        /// Name of this manager
        /// </summary>
        public string Name
        {
            get { return name; }
        }


        public Int64 ControlledEntity
        {
            get { return this.controlledEntity; }
        }

        public GameTime GameTimeData
        {
            get { return this.gameTimeData; }
        }

        #endregion

        #region Initialization


        /// <summary>
        /// Creates and initializes a scene manager.
        /// </summary>
        /// <param name="game">Base engine game class</param>
        public GameplayScreen(FrameworkCore game)
        {
            this.game = game;
            this.game.SceneManager = this;            

            this.interfaces = new Dictionary<InterfaceType, Interface>();
            this.scenes = new List<Scene>();
            this.entities = new Dictionary<Int64, BaseEntity>();

            this.game.Graphics.RegisterRenderChunkProvider(this);

            SettingUp();
        }

        /// <summary>
        /// Cleans up the SceneManager instance for deletion.
        /// </summary>
        ~GameplayScreen()
        {
            foreach (Interface qsInterface in this.interfaces.Values)
            {
                qsInterface.Shutdown();
            }
        }

        /// <summary>
        /// Shuts down all interfaces
        /// </summary>
        public void Shutdown()
        {
            foreach (Interface qsInterface in this.interfaces.Values)
            {
                qsInterface.Shutdown();
            }
        }

        /// <summary>
        /// Load graphics content for the game.
        /// </summary>
        public override void LoadContent(ContentManager contentManager)
        {
            this.content = contentManager;
            //this.content.RootDirectory = "Content";

            foreach (Scene scene in this.scenes)
            {
                scene.LoadContent(contentManager);
            }
        }

        private void SettingUp()
        {
            {
                ////////////////////////////////////////////////////////////////////////////
                // This must always remain the first entity created
                this.sceneMgrRootEntity = new BaseEntity(game);
                this.sceneMgrRootEntity.Name = "Scene Manager Root Entity";

                this.AddEntity(sceneMgrRootEntity);
                ////////////////////////////////////////////////////////////////////////////

                this.cameraInterface = new CameraInterface(game, InterfaceType.Camera);
                AddInterface(cameraInterface);

                this.physicsInterface = new PhysicsInterface(game, InterfaceType.Physics);
                AddInterface(physicsInterface);

                AddInterface(this.game.Graphics);

                SetupBaseCamera();

                MsgSetGravity msgSetGrav = ObjectPool.Aquire<MsgSetGravity>();
                msgSetGrav.Gravity = new Vector3(0.0f, -60.00f, 0.0f);
                this.game.SendInterfaceMessage(msgSetGrav, InterfaceType.Physics);

                // Start the first physics frame.
                MsgBeginPhysicsFrame msgBeginFrame = ObjectPool.Aquire<MsgBeginPhysicsFrame>();
                msgBeginFrame.GameTime = new GameTime();
                this.game.SendInterfaceMessage(msgBeginFrame, InterfaceType.Physics);
            }

            // once the load has finished, we use ResetElapsedTime to tell the game's
            // timing mechanism that we have just finished a very long frame, and that
            // it should not try to catch up.
            //ScreenManager.Game.ResetElapsedTime();
        }

        /// <summary>
        /// Adds an entity to the overall scene.
        /// </summary>
        /// <param name="inEntity">Entity to add to scene</param>        
        public void AddEntity(BaseEntity entity)
        {
            this.entities.Add(entity.UniqueID, entity);
        }

        /// <summary>
        /// Removes an entity from the overall scene.
        /// </summary>
        /// <param name="targetEntity">Entity to remove from scene</param>
        /// <remarks>TEMPORARY until we have a manager to remove entities marked for deletion</remarks>
        private void RemoveEntity(BaseEntity entity)
        {
            this.entities.Remove(entity.UniqueID);
        }

        private void ShutdownEntity(BaseEntity entity)
        {
            // If entity is in a scene
            if (entity != null)
            {
                // Send a shutdown to the entity
                entity.Shutdown();

                // Shutdown should be complete, now pull it from the scene
                RemoveEntity(entity);
                entity = null;
            }
        }

        /// <summary>
        /// Sets up the base camera to start off the scene manager.
        /// </summary>
        /// <remarks>This will likely be temporary until we are reading in a camera from the editor.</remarks>
        public void SetupBaseCamera()
        {
            // Create a free camera for the scene manager to use            
            CameraComponent sceneMgrCamera = new CameraComponent(this.sceneMgrRootEntity, 60.0f, this.game.Settings.Resolution.X, this.game.Settings.Resolution.Y, 0.5f, 2000.0f);

            // Lets create a free-cam
            CreateAndStartFreeMovingCamera();

            // Set our camera's position
            this.sceneMgrRootEntity.Position = new Vector3(800, 360, 780);

            // Set our camera's look-at point            
            this.sceneMgrRootEntity.LookAt(new Vector3(900, 360, 700));
        }

        private void CreateAndStartFreeMovingCamera()
        {
            if (currentCameraInputType != null)
            {
                if (currentCameraInputType is FreeCameraInputComponent)
                    return;

                this.sceneMgrRootEntity.RemoveComponent(currentCameraInputType);

                currentCameraInputType = null;
            }

            currentCameraInputType = new FreeCameraInputComponent(this.sceneMgrRootEntity);

            // Set a new render camera
            MsgSetRenderEntity camSetRenderMsg = ObjectPool.Aquire<MsgSetRenderEntity>();
            camSetRenderMsg.Entity = this.sceneMgrRootEntity;
            this.game.SendInterfaceMessage(camSetRenderMsg, InterfaceType.Camera);
        }

        private void CreateAndStartArcBallCamera()
        {
            BaseEntity entity;
            if (entities.TryGetValue(this.controlledEntity, out entity))
            {
                MsgSetParent msgSetParent = ObjectPool.Aquire<MsgSetParent>();
                msgSetParent.ParentEntity = entity;
                msgSetParent.UniqueTarget = this.sceneMgrRootEntity.UniqueID;
                this.game.SendMessage(msgSetParent);
            }

            if (currentCameraInputType != null)
            {
                if (currentCameraInputType is ArcBallCameraInputComponent)
                    return;

                this.sceneMgrRootEntity.RemoveComponent(currentCameraInputType);
            }

            ArcBallCameraInputComponent arcBallCam = new ArcBallCameraInputComponent(this.sceneMgrRootEntity);
            currentCameraInputType = arcBallCam;

            arcBallCam.TargetPosition = new Vector3(950, 400, 950);
            arcBallCam.LookAheadDistance = 10.0f;
            arcBallCam.LookAboveDistance = 5.0f;
            arcBallCam.HorizontalAngle = -1.3f;
            arcBallCam.VerticalAngle = 1.2f;
        }

        /// <summary>
        /// Forwards a message from the message system to the appropriate <see cref="QSInterface"/>.
        /// This function MUST be called only from QSGame, otherwise it will never mark the message properly for release
        /// and it will leak memory.
        /// </summary>
        /// <param name="message">Incoming message</param>
        /// <param name="interfaceType">Interface type to send message to</param>
        /// <returns>Returns false if interface was not found</returns>
        public bool ForwardInterfaceMessage(IMessage message, InterfaceType interfaceType)
        {
            if (interfaceType == InterfaceType.SceneManager)
            {
                return this.ExecuteMessage(message);
            }

            Interface qsInterface;
            if (!this.interfaces.TryGetValue(interfaceType, out qsInterface))
                return false;

            return qsInterface.ExecuteMessage(message);
        }

        /// <summary>
        /// Message handler for messages meant only for the scene manager.
        /// </summary>
        /// <param name="message">Incoming message</param>
        /// <exception cref="ArgumentException">Thrown if a <see cref="MessageType"/> is not handled properly."/></exception>
        public override bool ExecuteMessage(IMessage message)
        {
            switch (message.Type)
            {
                case MessageType.RemoveEntity:
                    {
                        MsgRemoveEntity remEntityMsg = message as MsgRemoveEntity;
                        message.TypeCheck(remEntityMsg);

                        if (remEntityMsg.EntityID != FrameworkCore.UniqueIDEmpty)
                        {
                            BaseEntity entity;
                            if (this.entities.TryGetValue(remEntityMsg.EntityID, out entity))
                            {
                                ShutdownEntity(entity);
                            }
                        }
                    }
                    return true;
                case MessageType.GetEntityIDList:
                    {
                        MsgGetEntityIDList msgGetEntityIDs = message as MsgGetEntityIDList;
                        message.TypeCheck(msgGetEntityIDs);

                        msgGetEntityIDs.EntityIDList = new Int64[this.entities.Count];
                        this.entities.Keys.CopyTo(msgGetEntityIDs.EntityIDList, 0);
                    }
                    return true;
                case MessageType.SetControlledEntity:
                    {
                        MsgSetControlledEntity msgSetControlled = message as MsgSetControlledEntity;
                        message.TypeCheck(msgSetControlled);

                        Int64 oldControlledEntity = this.controlledEntity;

                        this.controlledEntity = msgSetControlled.ControlledEntityID;

                        // Was there a change
                        if (oldControlledEntity != this.controlledEntity)
                        {
                            if (oldControlledEntity != FrameworkCore.UniqueIDEmpty)
                            {
                                MsgRemoveChild msgRemoveChild = ObjectPool.Aquire<MsgRemoveChild>();
                                msgRemoveChild.Child = this.sceneMgrRootEntity;
                                msgRemoveChild.UniqueTarget = oldControlledEntity;
                                this.game.SendMessage(msgRemoveChild);

                                MsgSetIsControlled msgControlled = ObjectPool.Aquire<MsgSetIsControlled>();
                                msgControlled.Controlled = false;
                                msgControlled.UniqueTarget = oldControlledEntity;
                                this.game.SendMessage(msgControlled);
                            }

                            // If the new ID is empty, there is no more controlled entity
                            if (this.controlledEntity == FrameworkCore.UniqueIDEmpty)
                            {
                                CreateAndStartFreeMovingCamera();
                            }
                            else
                            {
                                MsgSetIsControlled msgControlled = ObjectPool.Aquire<MsgSetIsControlled>();
                                msgControlled.Controlled = true;
                                msgControlled.UniqueTarget = this.controlledEntity;
                                this.game.SendMessage(msgControlled);

                                BaseEntity controlledEntity;
                                if (entities.TryGetValue(this.controlledEntity, out controlledEntity))
                                {
                                    CreateAndStartArcBallCamera();
                                }
                            }
                        }
                    }
                    return true;
                case MessageType.GetControlledEntity:
                    {
                        MsgGetControlledEntity msgGetControlled = message as MsgGetControlledEntity;
                        message.TypeCheck(msgGetControlled);

                        msgGetControlled.ControlledEntityID = this.controlledEntity;
                    }
                    return true;
                case MessageType.GetEntityByID:
                    {
                        MsgGetEntityByID msgGetEntity = message as MsgGetEntityByID;
                        message.TypeCheck(msgGetEntity);

                        BaseEntity entity;
                        if (this.entities.TryGetValue(msgGetEntity.EntityID, out entity))
                        {
                            msgGetEntity.Entity = entity;
                        }
                    }
                    return true;
                case MessageType.GetFogSettings:
                    {
                        if (this.activeScene != null)
                        {
                            MsgGetFogSettings msgGetFog = message as MsgGetFogSettings;
                            message.TypeCheck(msgGetFog);

                            msgGetFog.Settings = this.activeScene.FogSettings;
                        }
                    }
                    return true;
                default:
                    return false;
            }
        }


        /// <summary>
        /// Unload graphics content used by the game.
        /// </summary>
        public override void UnloadContent()
        {
            // Why don't do this
            //content.Unload();
        }

        /// <summary>
        /// Queries for all potentially visible RenderChunk instances given a RenderPassDesc descriptor.
        /// </summary>
        /// <param name="desc">A descriptor for the current rendering pass.</param>
        public void QueryForChunks(ref RenderPassDesc desc)
        {
            foreach (BaseEntity entity in this.entities.Values)
            {
                entity.QueryForRenderChunks(ref desc);
            }
        }

        /// <summary>
        /// Queries for Skinned Model Chunks
        /// </summary>
        /// <param name="desc">A descriptor for the current rendering pass.</param>
        public void QueryForSkinnedModelChunks(ref RenderPassDesc desc)
        {
            foreach (BaseEntity entity in this.entities.Values)
            {
                entity.QueryForSkinnedModelChunks(ref desc);
            }
        }

        /// <summary>
        /// Adds a <see cref="QSInterface"/> to the <see cref="SceneManager"/>.
        /// </summary>
        /// <param name="inQSInterface"><see cref="QSInterface"/> to add.</param>
        private void AddInterface(Interface inQSInterface)
        {
            Interface qsInterface;
            if (interfaces.TryGetValue(inQSInterface.InterfaceID, out qsInterface))
            {
                throw new Exception("An interface by this ID already exists");
            }

            this.interfaces.Add(inQSInterface.InterfaceID, inQSInterface);
        }


        #endregion

        #region Update and Draw


        /// <summary>
        /// Updates the state of the game. This method checks the GameScreen.IsActive
        /// property, so the game will stop updating when the pause menu is active,
        /// or if you tab away to a different application.
        /// </summary>
        public override void Update(GameTime gameTime, bool otherScreenHasFocus, bool coveredByOtherScreen)
        {
            base.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen);

            if (IsActive)
            {
                gameTimeData = gameTime;

                // Update all interfaces
                foreach (Interface qsInterface in this.interfaces.Values)
                {
                    qsInterface.Update(gameTime);
                }

                // End the current physics frame
                MsgEndPhysicsFrame msgEndFrame = ObjectPool.Aquire<MsgEndPhysicsFrame>();
                this.game.SendInterfaceMessage(msgEndFrame, InterfaceType.Physics);

                foreach (BaseEntity entity in this.entities.Values)
                {
                    entity.Update(gameTime);
                }

                // Begin the next physics frame
                MsgBeginPhysicsFrame msgBeginFrame = ObjectPool.Aquire<MsgBeginPhysicsFrame>();
                msgBeginFrame.GameTime = gameTime;
                this.game.SendInterfaceMessage(msgBeginFrame, InterfaceType.Physics);
            }

            if (this.game.FrameworkCoreExiting)
            {
                // Send a single shutdown message to all, bypass the pool as we are shutting down                
                this.Shutdown();
            }
        }



        /// <summary>
        /// Draws the gameplay screen.
        /// </summary>
        public override void Draw(GameTime gameTime)
        {
            if (this.cameraInterface.RenderCamera != null)
            {
                this.game.Graphics.DrawFrame(this.cameraInterface.RenderCamera, gameTime);
            }

            // If the game is transitioning on or off, fade it out to black.
            if (TransitionPosition > 0)
                ScreenManager.FadeBackBufferToBlack(255 - TransitionAlpha);
        }


        #endregion

        #region Load Scene

        public void SetLevel(string level)
        {
            Type type = Type.GetType("Alex_s_Dream_The_Toy_World." + level);
            if (type == null)
            {
                throw new TypeLoadException(string.Format(CultureInfo.InvariantCulture, "Could not find manager type '{0}'", level));
            }

            Scene levelScene = Activator.CreateInstance(type, game) as Scene;
            if (levelScene == null)
            {
                throw new TypeLoadException(string.Format(CultureInfo.InvariantCulture, "Manager type '{0}' was not found", level));
            }

            this.ActiveScene = levelScene;
            this.LoadScene(levelScene, true);
        }

        /// <summary>
        /// Buffer and load a <see cref="Scene"/> into memory.
        /// </summary>
        /// <param name="targetScene"><see cref="Scene"/> to load</param>
        /// <param name="autoBegin">Begin a <see cref="Scene"/> once loaded</param>
        public void LoadScene(Scene targetScene, bool autoBegin)
        {
            // We need a scene loader, which should probably be its own class.
            // Scene loading should be buffered, preferably in its own thread.

            // If a scene is to be buffered in, would we ever want to begin before all of it
            // is loaded?

            // *** TEMP ***
            // Currently this method doesn't buffer anything, it simply adds a given scene
            // to a list. Eventually the scene should be loaded from an XML file. XML file can contain
            // all pertainant info about the scene, like which models to load, where to place them, what
            // kind of sky dome, what strength of gravity, etc. The loader will create a scene out of this
            // and then start it if needed.

            AddScene(targetScene);

            if (autoBegin)
            {
                BeginScene(targetScene);
            }
        }

        /// <summary>
        /// Adds a <see cref="Scene"/> to the <see cref="SceneManager"/>.
        /// </summary>
        /// <param name="inScene">Scene to add</param>
        private void AddScene(Scene inScene)
        {
            // @todo: Should we check here if scene is finished loading?
            this.scenes.Add(inScene);
        }

        /// <summary>
        /// Start running a new <see cref="Scene"/>. After beginning a scene it will continue to run
        /// until stopped. Scene must be loaded before it can begin.
        /// </summary>
        /// <param name="targetScene"><see cref="Scene"/> to run.</param>
        public void BeginScene(Scene targetScene)
        {
            targetScene.Activate();
        }

        /// <summary>
        /// Unload a <see cref="Scene"/> from memory.
        /// </summary>
        /// <param name="targetScene">Scene to unload</param>
        public void UnloadScene(Scene targetScene)
        {
            // We need a scene unload, which should probably be in a separate class.
            // Could be combined with the scene loader. Scene unloading doesn't need
            // to be buffered, but should still be done in its own scene.

            // Scene should not be able to unload unless stopped first.

            // Any other conditions required for unloading?
            StopScene(targetScene);

            this.scenes.Remove(targetScene);
        }

        /// <summary>
        /// Stops <see cref="Scene"/> from being processes. This step must be done before unloading a scene.
        /// </summary>
        /// <param name="targetScene"><see cref="Scene"/> to stop.</param>
        public void StopScene(Scene targetScene)
        {
            targetScene.Deactiviate();
        }

        #endregion
    }
}
